package com.cloudy.utility;

import com.cloudy.exception.S3AccessException;
import com.cloudy.impl.AwsURLEncoder;
import com.cloudy.s3.request.S3Request;
import com.cloudy.s3.response.MetadataEntry;
import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;
import org.xml.sax.SAXException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class CloudyUtility {
    public static String getQueryString(Map<String, String> attributes) {
        StringBuilder query = new StringBuilder();
        for (Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext();) {
            String key = iterator.next();
            String value = attributes.get(key);
            query.append(AwsURLEncoder.urlEncode(key)).append("=").append(AwsURLEncoder.urlEncode(value)).append("&");
        }
        String result = query.toString();
        if (result != null && !result.equals(""))
            result = "?" + result.substring(0, result.length() - 1);
        return result;
    }

    public static String createStringToSign(S3Request request) {
        String contentMD5 = request.getContentMD5();
        String contentType = request.getContentType();
        String date = request.getTimeStamp();
        String method = request.getRequestMethod();
        String bucket = request.getBucket();
        bucket = (bucket == null || bucket.equals("")) ? "" : bucket + "/";
        String stringToSign = new StringBuilder()
                .append(method).append("\n")
                .append(contentMD5).append("\n")
                .append(contentType).append("\n")
                .append(date).append("\n")
                .append(request.getCanonicalizedAmzHeaders()).append("/").append(bucket).append(request.getResource()).append(request.getSubResource()).toString();
        return stringToSign;
    }

    public static Object unmarshalTheResponse(InputStream stream) throws IOException, JAXBException {
       JAXBContext ctx = JAXBContext.newInstance("com.cloudy.ec2.response:com.cloudy.s3.response");
       return unmarshalTheResponse(ctx, stream);
    }

    public static Object unmarshalTheResponse(JAXBContext ctx, InputStream stream) throws IOException, JAXBException {
        Unmarshaller u = ctx.createUnmarshaller();
//        StringBuilder builder = new StringBuilder();
//        InputStreamReader reader = new InputStreamReader(stream);
//        BufferedReader bufferedReader = new BufferedReader(reader);
//        String line;
//        while ((line = bufferedReader.readLine()) != null) {
//            builder.append(line);
//        }
//        System.out.println("XML is");
//        System.out.println(builder.toString());
//        ByteArrayInputStream bis = new ByteArrayInputStream(builder.toString().getBytes());
//        Object o = u.unmarshal(bis);
        Object o = u.unmarshal(stream);
        if (o instanceof JAXBElement) {
            return ((JAXBElement) o).getValue();
        } else
            return o;
    }

    public static String marshalTheObject(Object obj) throws S3AccessException {
//		JAXBContext ctx;
//		ctx = JAXBContext.newInstance("com.cloudy.s3.response");
//		Marshaller m = ctx.createMarshaller();
//		ByteArrayOutputStream bos = new ByteArrayOutputStream();
//		m.marshal(obj, bos);
//		String result = new String(bos.toString());
//		int beginIndex = result.indexOf(obj.getClass().getSimpleName()) - 1;
//		result = result.substring(beginIndex);
//		return result;
        try {
            return object2Xml(obj);
        } catch (IllegalArgumentException e) {
            throw new S3AccessException(e);
        } catch (IllegalAccessException e) {
            throw new S3AccessException(e);
        } catch (InvocationTargetException e) {
            throw new S3AccessException(e);
        } catch (NoSuchFieldException e) {
            throw new S3AccessException(e);
        } catch (SecurityException e) {
            throw new S3AccessException(e);
        } catch (NoSuchMethodException e) {
            throw new S3AccessException(e);
        }
    }

    public static void transferBytesFromIn2Out(InputStream in, OutputStream out)
            throws IOException {
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
    }

    public static Map<String, MetadataEntry> getHeaders(HttpURLConnection httpConnection) {
//		Headers headers = new Headers();
        Map<String, MetadataEntry> map = new HashMap<String, MetadataEntry>();
        for (int i = 0; ; i++) {
            String headerName = httpConnection.getHeaderFieldKey(i);
            String headerValue = httpConnection.getHeaderField(i);
            if (headerName == null && headerValue == null) {
                break;
            }
//			headers.add(headerName, headerValue);
            MetadataEntry entry = new MetadataEntry();
            entry.setName(headerName);
            entry.setValue(headerValue);
            map.put(entry.getName(), entry);
//			System.out.println(map.keySet());
        }
        return map;
    }

    public static String object2Xml(Object object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchFieldException, SecurityException, NoSuchMethodException {
        StringBuffer xml = new StringBuffer();
        String classSimpleName = object.getClass().getSimpleName();
        xml.append("<").append(classSimpleName).append(">");
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            Method getter;
            if ((getter = getGetter(field, object)) != null)
                appendFields(getter.invoke(object), xml);
        }
        xml.append("</").append(classSimpleName).append(">");
        return xml.toString();
    }

    private static void appendFields(Object field, StringBuffer xml) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
        Method method = null;
        if (field instanceof Enum)
            method = field.getClass().getDeclaredMethod("value");
        else
            method = getGetter(field.getClass().getDeclaredField("value"), field);
        String elementName = field.getClass().getSimpleName();
        String elementValue = method.invoke(field).toString();
        constructElement(xml, elementValue, elementName);
    }

    private static void constructElement(StringBuffer xml, String elementValue,
                                         String elementName) {
        xml.append("<").append(elementName).append(">").append(elementValue).append("</").append(elementName).append(">");
    }

    private static Method getGetter(Field field, Object object) {
        Method[] methods = object.getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equalsIgnoreCase("get" + field.getName()))
                return method;
        }
        return null;
    }

    public static XMLGregorianCalendar parseDate(String value) throws SAXException {
        String dformat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
        return parseDate(value, dformat);
    }

    public static XMLGregorianCalendar parseDate(String value, String dformat)
            throws SAXException {
        SimpleDateFormat format = new SimpleDateFormat(dformat);
        Date d = null;
        try {
            d = format.parse(value);
        } catch (ParseException e) {
            throw new SAXException(e);
        }
        GregorianCalendar c = (GregorianCalendar) GregorianCalendar.getInstance();
        c.setTime(d);
        XMLGregorianCalendar date = new XMLGregorianCalendarImpl(c);
        return date;
    }
}
